﻿using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ChunkRender
{
    private Chunk chunk;
    private List<Vector3> Vertices = new List<Vector3>();
    private List<Vector2> UVs = new List<Vector2>();
    private List<int> Triangles = new List<int>();
    private int VerticeCount = 0;
    private VoxelTypeManager voxelTypeManager;

    private ChunkRender() { }
    public ChunkRender(Chunk target)
    {
        chunk = target;
        voxelTypeManager = VoxelTypeManager.Instance.GetComponent<VoxelTypeManager>();
    }

    public void Render()
    {
        if (chunk == null)
        {
            return;
        }
        foreach (Transform child in chunk.transform)
        {
            Chunk.Destroy(child.gameObject);
        }
        GenericChunkMesh();
        DrawMesh(chunk.GetComponent<MeshFilter>().mesh);
    }

    private void GenericChunkMesh()
    {
        if (chunk == null)
        {
            return;
        }
        for (int x = 0; x < Const.ChunkSideLength; ++x)
        {
            for (int y = 0; y < Const.ChunkSideLength; ++y)
            {
                for (int z = 0; z < Const.ChunkSideLength; ++z)
                {
                    VoxelType voxelType = voxelTypeManager.GetVoxelType(chunk.GetVoxelTypeID(x, y, z));
                    if (voxelType == null || (!voxelType.Visiblity))
                    {
                        continue;
                    }
                    if (voxelType.VCustomMesh)
                    {
                        AddCustomVoxelMesh(x, y, z, voxelType);
                    }
                    else
                    {
                        AddCubeVoxelMesh(x, y, z, voxelType);
                    }
                }
            }
        }
    }

    private void AddCubeVoxelMesh(int x, int y, int z, VoxelType voxelType)
    {
        Transparency transparency = voxelType.VTransparency;
        if (CheckFaceVisible(x, y, z + 1, Direction.forward, transparency) == true)
        { AddVoxelFace(voxelType, Facing.forward, x, y, z); }

        if (CheckFaceVisible(x, y, z - 1, Direction.back, transparency) == true)
        { AddVoxelFace(voxelType, Facing.back, x, y, z); }

        if (CheckFaceVisible(x, y + 1, z, Direction.up, transparency) == true)
        { AddVoxelFace(voxelType, Facing.up, x, y, z); }

        if (CheckFaceVisible(x, y - 1, z, Direction.down, transparency) == true)
        { AddVoxelFace(voxelType, Facing.down, x, y, z); }

        if (CheckFaceVisible(x + 1, y, z, Direction.right, transparency) == true)
        { AddVoxelFace(voxelType, Facing.right, x, y, z); }

        if (CheckFaceVisible(x - 1, y, z, Direction.left, transparency) == true)
        { AddVoxelFace(voxelType, Facing.left, x, y, z); }
    }

    private void AddCustomVoxelMesh(int x, int y, int z, VoxelType voxelType)
    {
        if (voxelType == null)
        {
            return;
        }
        Mesh mesh = voxelType.GetComponent<Mesh>();
        if (mesh == null)
        {
            return;
        }
        Vector3 basePos = new Vector3(x, y, z);
        foreach (Vector3 vertex in mesh.vertices)
        {
            Vertices.Add(vertex + basePos);
        }
        UVs.AddRange(mesh.uv);
        // faces
        foreach (int vertexIndex in mesh.triangles)
        {
            Triangles.Add(VerticeCount + vertexIndex);
        }
        VerticeCount += mesh.vertexCount;
    }

    private bool CheckFaceVisible(int x, int y, int z, Direction direction, Transparency transparency)
    {
        VoxelType voxelType = voxelTypeManager.GetVoxelType(chunk.GetVoxelTypeID(x, y, z));
        // VoxelType voxelType = voxelTypeManager.GetVoxelType(chunk.GetVoxelSimple(x, y, z));
        if (voxelType != null && voxelType.VTransparency == Transparency.solid)
        {
            return false;
        }
        return true;
    }

    private void AddVoxelFace(VoxelType voxelType, Facing facing, int x, int y, int z)
    {
        switch (facing)
        {
            case Facing.forward:
                Vertices.Add(new Vector3(x + 0.5001f, y - 0.5001f, z + 0.5f));
                Vertices.Add(new Vector3(x + 0.5001f, y + 0.5001f, z + 0.5f));
                Vertices.Add(new Vector3(x - 0.5001f, y + 0.5001f, z + 0.5f));
                Vertices.Add(new Vector3(x - 0.5001f, y - 0.5001f, z + 0.5f));
                break;
            case Facing.back:
                Vertices.Add(new Vector3(x - 0.5001f, y - 0.5001f, z - 0.5f));
                Vertices.Add(new Vector3(x - 0.5001f, y + 0.5001f, z - 0.5f));
                Vertices.Add(new Vector3(x + 0.5001f, y + 0.5001f, z - 0.5f));
                Vertices.Add(new Vector3(x + 0.5001f, y - 0.5001f, z - 0.5f));
                break;
            case Facing.up:
                Vertices.Add(new Vector3(x - 0.5001f, y + 0.5f, z - 0.5001f));
                Vertices.Add(new Vector3(x - 0.5001f, y + 0.5f, z + 0.5001f));
                Vertices.Add(new Vector3(x + 0.5001f, y + 0.5f, z + 0.5001f));
                Vertices.Add(new Vector3(x + 0.5001f, y + 0.5f, z - 0.5001f));
                break;
            case Facing.down:
                Vertices.Add(new Vector3(x - 0.5001f, y - 0.5f, z + 0.5001f));
                Vertices.Add(new Vector3(x - 0.5001f, y - 0.5f, z - 0.5001f));
                Vertices.Add(new Vector3(x + 0.5001f, y - 0.5f, z - 0.5001f));
                Vertices.Add(new Vector3(x + 0.5001f, y - 0.5f, z + 0.5001f));
                break;
            case Facing.right:
                Vertices.Add(new Vector3(x + 0.5f, y - 0.5001f, z - 0.5001f));
                Vertices.Add(new Vector3(x + 0.5f, y + 0.5001f, z - 0.5001f));
                Vertices.Add(new Vector3(x + 0.5f, y + 0.5001f, z + 0.5001f));
                Vertices.Add(new Vector3(x + 0.5f, y - 0.5001f, z + 0.5001f));
                break;
            case Facing.left:
                Vertices.Add(new Vector3(x - 0.5f, y - 0.5001f, z + 0.5001f));
                Vertices.Add(new Vector3(x - 0.5f, y + 0.5001f, z + 0.5001f));
                Vertices.Add(new Vector3(x - 0.5f, y + 0.5001f, z - 0.5001f));
                Vertices.Add(new Vector3(x - 0.5f, y - 0.5001f, z - 0.5001f));
                break;
            default:
                break;
        }
        Vector2 textureOffset = (voxelType != null) ? voxelType.GetTextureOffset(facing) : new Vector2(0, 0);
        // Vector2 textureOffset = voxelType.GetTextureOffset(facing);
        textureOffset *= 0.125f;
        UVs.Add(new Vector2(textureOffset.x + 0.0f, textureOffset.y + 0.0f));
        UVs.Add(new Vector2(textureOffset.x + 0.0f, textureOffset.y + 0.125f));
        UVs.Add(new Vector2(textureOffset.x + 0.125f, textureOffset.y + 0.125f));
        UVs.Add(new Vector2(textureOffset.x + 0.125f, textureOffset.y + 0.0f));
        Triangles.Add(VerticeCount + 0);
        Triangles.Add(VerticeCount + 1);
        Triangles.Add(VerticeCount + 2);
        Triangles.Add(VerticeCount + 0);
        Triangles.Add(VerticeCount + 2);
        Triangles.Add(VerticeCount + 3);
        VerticeCount += 4;
    }

    private void DrawMesh(Mesh mesh)
    {
        mesh.Clear();
        mesh.vertices = Vertices.ToArray();
        mesh.uv = UVs.ToArray();
        mesh.subMeshCount = 1;
        mesh.triangles = Triangles.ToArray();
        mesh.Optimize();
        mesh.RecalculateNormals();

        Mesh colMesh = new Mesh();
        colMesh.vertices = mesh.vertices;
        colMesh.triangles = mesh.triangles;
        colMesh.Optimize();
        colMesh.RecalculateNormals();
        chunk.GetComponent<MeshCollider>().sharedMesh = null;
        chunk.GetComponent<MeshCollider>().sharedMesh = colMesh;

        Vertices.Clear();
        UVs.Clear();
        Triangles.Clear();
        VerticeCount = 0;
    }
    // private void AddNewMeshObject() {
    //     GameObject 
    // }
    // private void CreateNewMeshObject () { // in case the amount of vertices exceeds the maximum for one mesh, we need to create a new mesh

    // 	GameObject meshContainer = Instantiate(chunk.MeshContainer, transform.position, transform.rotation) as GameObject;
    // 	meshContainer.transform.parent = this.transform;

    // 	UpdateMesh ( meshContainer.GetComponent<MeshFilter>().mesh );
    // }
}
